package database.singleDB;

import aof.inter.IAddAof;
import config.ServerConfig;
import database.inter.*;
import datastruct.dict.impl.JoConcurrentMap;
import datastruct.dict.inter.IDict;
import datastruct.dict.inter.IDictConsumer;
import datastruct.lock.Locks;
import jodis.conn.impl.JoConnection;
import jodis.conn.impl.StandardErrReply;
import jodis.conn.inter.IReply;
import rdb.RDBConstant;

import java.util.Locale;

public class SingleDB implements IDB {
    public int index;

    private IDict dataMap;

    // 记录下
    private IDict typeMap;

    // ttlMap 里存的是 key -> 毫秒时间戳
    private IDict ttlMap;
    private IDict versionMap;

    public IAddAof iAddAof;

    private Locks locks;

    // todo aof

    // todo cmdSync

    public SingleDB() {
        dataMap = new JoConcurrentMap();
        typeMap = new JoConcurrentMap();
        ttlMap = new JoConcurrentMap();
        versionMap = new JoConcurrentMap();
        locks = Locks.makeLock(1<<16);
    }

    public void putType(String key, int type) {
        typeMap.put(key, type);
    }

    public int getType(String key) {
        return typeMap.contains(key) ? (int)typeMap.get(key) : RDBConstant.TYPE_NONE;
    }

    public void putTTL(String key, long timestamp) {
        ttlMap.put(key, timestamp);
    }

    public Long getTTL(String key) {
        return ttlMap.contains(key) ? (Long) ttlMap.get(key) : -1;
    }

    public IDict getTTLMap() {
        return ttlMap;
    }

    public static SingleDB makeDB() {
        return new SingleDB();
    }

    public Object getEntity(String key) {
        return dataMap == null || dataMap.len() == 0 ? null : dataMap.get(key);
    }

    public int putEntity(String key, Object entity) {
        if (dataMap == null) {
            return 0;
        }
        return dataMap.put(key, entity);
    }

    public void removeEntity(String key) {
        dataMap.remove(key);
        ttlMap.remove(key);
        typeMap.remove(key);
        versionMap.remove(key);
    }
    
    public int putIfAbsent(String key, Object entity) {
        return dataMap.putIfAbsent(key, entity);
    }

    public int putIfExists(String key, Object val) {
        return dataMap.putIfExists(key, val);
    }

    @Override
    public IReply exec(JoConnection clientConn, String[] cmdLine) {
        // todo 处理事务相关命令

        // 处理普通命令
        // 执行命令前，统一检查 key 是否过期，进行惰性删除
        if (cmdLine.length > 1) {
            expireIfNeeded(cmdLine[1]);
        }

        return execCommand(cmdLine);
    }

    public void expireIfNeeded(String key) {
        if (ttlMap.contains(key)) {
            Long ttl = (Long) ttlMap.get(key);
            if (ttl <= System.currentTimeMillis()) {
                dataMap.remove(key);
                ttlMap.remove(key);
            }
        }
    }

    private IReply execCommand(String[] cmdLine) {
        String cmdName = cmdLine[0].toLowerCase();
        if (!Command.cmdExecTable.containsKey(cmdName)) {
            return StandardErrReply.makeErrReply(String.format("unknown command '%s'", cmdName));
        }

        ICommandPreRead iCommandPreRead = Command.cmdPreReadTable.get(cmdName);
        ICommandPreWrite iCommandPreWrite = Command.cmdPreWriteTable.get(cmdName);
        ICommandUndo iCommandUndo = Command.cmdUndoTable.get(cmdName);
        String[] writeKeys = iCommandPreWrite == null ? null : iCommandPreWrite.preWriteKeyFunc(cmdLine);
        String[] readKeys = iCommandPreRead == null ? null : iCommandPreRead.preReadKeyFunc(cmdLine);
        if (iCommandUndo != null) {
            iCommandUndo.undoFunc(this, cmdLine);
        }

        // 对所有的 key 进行锁定
        try {
            rwLock(writeKeys, readKeys);
            return Command.cmdExecTable.get(cmdName).execFunc(this, cmdLine);
        } finally {
            rwUnLock(writeKeys, readKeys);
        }
    }

    public void forEach(IDictConsumer consumer) {
        dataMap.forEach(consumer);
    }

    public int getKeyCount() {
        return dataMap.len();
    }

    public int getTTLCount() {
        return ttlMap.len();
    }

    private void rwLock(String[] writeKeys, String[] readKeys) {
        locks.rwLocks(writeKeys, readKeys);
    }

    private void rwUnLock(String[] writeKeys, String[] readKeys) {
        locks.rwUnLocks(writeKeys, readKeys);
    }
}
